package view;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.awt.print.PageFormat;
import java.awt.print.Printable;
import java.io.IOException;import java.util.ArrayList;import javax.imageio.ImageIO;
import javax.swing.JPanel;
import view.menus.RightClickBGMenu;
import view.menus.RightClickMenu;
import controller.CanvasMouseListener;
import controller.CanvasMouseMotionListener;
import model.Prescription;
import model.PrescriptionObject;
import model.PrescriptionBox;import model.PrescriptionLabel;/** * A class to encapsulate the canvas functionality * @author Daniel Bond, Mohammed Abdullah, Gideon Pyzer * */
@SuppressWarnings("serial")
public class Canvas extends JPanel implements Printable{

	//Use to import image
	private BufferedImage watermark;

	private boolean isSelected;
	private boolean isDrag;
	private boolean isResize;

	//Define the different types of strokes used on canvas
	private final static float dots[] = {2.0f};
	private final static BasicStroke dotted = new BasicStroke(1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 2.0f, dots, 0.0f);

	//Used when objects are being dragged
	private int draggedX;
	private int draggedY;
	private int draggedHeight;
	private int draggedWidth;

	//Used for setting resize dimensions
	private int resizeX;
	private int resizeY;
	private int resizeWidth;
	private int resizeHeight;

	//Properties for object currently selected
	private int objectX;
	private int objectY;
	private int objectWidth;
	private int objectHeight;

	//Start coordinates of mouse, used in resize and dragging
	private int startX;
	private int startY;

	//Current coordinates of mouse cursor
	private int currentX;
	private int currentY;

	//Temporary variable for change in x and y, used in resize and dragging
	public int changeX;
	public int changeY;

	//Current values for object selected
	public int boxCurrentX;
	public int boxCurrentY;
	public int currentWidth;
	public int currentHeight;
	public int boxCurrentWidth;
	public int boxCurrentHeight;

	//Right Click Menus
	private RightClickMenu rightClickMenu;
	private RightClickBGMenu rightClickBGMenu;

	//Canvas Instance
	private static Canvas instance;

	//Constructor
	private Canvas(){
		this.addMouseMotionListener(new CanvasMouseMotionListener());
		this.addMouseListener(new CanvasMouseListener());


		//Read file as image
		try {
			watermark = ImageIO.read(Designer.class.getResource("/images/watermark.png"));
		} catch (IOException e) {
			e.printStackTrace();
		}


		isSelected = false;

		rightClickMenu = new RightClickMenu();
		rightClickBGMenu = new RightClickBGMenu();
	}

	/**
	 * Used to enforce a singleton instance
	 * @return Canvas instance
	 */
	public static Canvas getInstance(){
		if(instance == null){
			instance = new Canvas();
		}

		return instance;
	}

	/**	 * Checks if the canvas is resizing an object
	 * @return true if canvas is performing a resize on object
	 */
	public boolean isResize(){
		return isResize;
	}

	/**
	 * Sets the resize boolean, 
	 * @param resize true if the canvas is resizing an object, false otherwise
	 */
	public void setResize(boolean resize){
		isResize = resize;
	}

	/**	 * Checks if the canvas is currently dragging an object
	 * @return true if canvas is dragging an object, false otherwise
	 */
	public boolean isDrag(){
		return isDrag;
	}

	/**	 * Sets the isDrag attribute
	 * @param drag true if canvas is performing a drag, false otherwise
	 */
	public void setDrag(boolean drag){
		isDrag = drag;
	}

	/**	 * Checks if an object is currently selected
	 * @return true if an object is selected, false otherwise
	 */
	public boolean isSelected(){
		return isSelected;
	}

	/**	 * Sets the isSelected attribute
	 * @param selected true if an object is currently selected, false otherwise
	 */
	public void setSelected(boolean selected){
		isSelected = selected;
	}

	/**	 * Gets the x coordinate of the selected object
	 * @return the x coordinate of selected object
	 */
	public int getObjectX(){
		return objectX;
	}

	/**	 * Gets the y coordinate of the selected object
	 * @return y coordinate of selected object
	 */
	public int getObjectY(){
		return objectY;
	}

	/**	 * Gets the width of the selected object
	 * @return width of selected object
	 */
	public int getObjectWidth(){
		return objectWidth;
	}

	/**	 * Gets the height of the selected object
	 * @return height of selected object
	 */
	public int getObjectHeight(){
		return objectHeight;
	}

	/**	 * Gets the x coordinate of the resized object
	 * @return x coordinate of resize object
	 */
	public int getResizeX(){
		return resizeX;
	}

	/**	 * Gets the y coordinate of the resized object
	 * @return y coordinate of resize object
	 */
	public int getResizeY(){
		return resizeY;
	}

	/**	 * Gets the width of the resized object
	 * @return width of resize object
	 */
	public int getResizeWidth(){
		return resizeWidth;
	}

	/**	 * Gets the height of the resized object
	 * @return height of resize object
	 */
	public int getResizeHeight(){
		return resizeHeight;
	}

	/**
	 * Set the dimensions for the resize object
	 * @param x - x coordinate
	 * @param y - y coordinate
	 * @param width - width in pixels
	 * @param height - height in pixels
	 */
	public void setResizeDimension(int x, int y, int width, int height){
		resizeX = x;
		resizeY = y;
		resizeWidth = width;
		resizeHeight = height;
	}

	/**
	 * Set object dimensions
	 * @param x coordinate 
	 * @param y coordinate
	 * @param width of object
	 * @param heigh of object
	 */
	public void setObjectDimensions(int x, int y, int width, int height){
		objectX = x;
		objectY = y;
		objectWidth = width;
		objectHeight = height;
	}

	/**	 * Gets the starting x coordinate position of the mouse
	 * @return starting x coordinate of mouse
	 */
	public int getStartX(){
		return startX;
	}

	/**	 * Gets the starting y coordinate position of the mouse
	 * @return starting y coordinate of mouse
	 */
	public int getStartY(){		return startY;
	}

	/**
	 * Sets the starting coordinate of mouse
	 * @param x the x coordinate of the mouse
	 * @param y the y coordinate of the mouse
	 */
	public void setStartCoordinates(int x, int y){
		startX = x;
		startY = y;
	}

	/**	 * Gets the current x coordinate
	 * @return current x coordinate
	 */
	public int getCurrentX(){
		return currentX;
	}

	/**	 * Gets the current y coordinate
	 * @return current y coordinate
	 */
	public int getCurrentY(){
		return currentY;
	}

	/**
	 * Sets the current coordinates
	 * @param x the current x coordinate
	 * @param y the current y coordinate
	 */
	public void setCurrentCoordinates(int x, int y){
		currentX = x;
		currentY = y;
	}

	/**	 * Gets the x coordinate of the dragged object
	 * @return x coordinate of drag function
	 */
	public int getDragX(){
		return draggedX;
	}

	/**	 * Gets the y coordinate of the dragged object
	 * @return y coordinate of drag function
	 */
	public int getDragY(){
		return draggedY;
	}

	/**	 * Gets the width of the dragged object
	 * @return width of drag function
	 */
	public int getDragWidth(){
		return draggedWidth;
	}

	/**	 * Gets the height of the dragged object
	 * @return height of drag function
	 */
	public int getDragHeight(){
		return draggedHeight;
	}

	/**
	 * Set the drag function dimensions and position
	 * @param x coordinate
	 * @param y coordinate
	 * @param width in pixels
	 * @param height in pixels
	 */
	public void setDraggedDimensions(int x, int y, int width, int height){
		draggedX = x;
		draggedY = y;
		draggedWidth = width;
		draggedHeight = height;
	}

	/**	 * Sets the dragged x coordinate
	 * @param x set the x coordinate
	 */
	public void setDraggedX(int x){
		draggedX = x;
	}

	/**	 * Sets the dragged y coordinate
	 * @param y set the y coordinate
	 */
	public void setDraggedY(int y){
		draggedY = y;
	}



	/**	 * Standard paint function	 * @param g the graphics object to paint	 */
	public void paint(Graphics g){
		Graphics2D g2 = (Graphics2D) g;
		renderEditor(g2);
	}

	/**This method creates a translucent rectangle around the selected object.
	 * 
	 * @param g - the graphics object used to create the drawing
	 * @param x - Integer, x coordinate of the object which is selected
	 * @param y - Integer, y coordinate of the object which is selected
	 * @param width - width on selected object
	 * @param height - height of selected object
	 */
	private void drawSelectionRec(Graphics g, int x, int y, int width, int height){

		//These are the default colour settings
		final Color selectLine = new Color(0.4f, 0.3f, 0.5f, 0.24f);
		Graphics2D g2 = (Graphics2D) g;
		g2.setStroke(new BasicStroke(1.0f));
		g2.setColor(selectLine);
		g2.drawRoundRect((x - 5), (y - 5), (width + 10), (height + 10), 10, 10);	
	}

	/**
	 * This method draws the resizing squares on the bottom right on object.
	 * @param g - the graphics object used for drawing
	 * @param x - x coordinate of resize square
	 * @param y - y coordinate or resize square
	 */
	private void drawResizeSquare(Graphics g, int x, int y){
		Graphics2D g2 = (Graphics2D) g;
		g2.setColor(Color.BLACK);
		g2.fillRect(x, y, 5, 5);
	}

	/**
	 * Main rendering method to paint all objects, colours and drag modes<br/>
	 * Called every time mouse is moved over canvas, clicked or dragged, and
	 * when something on the prescription changes.
	 * <br/><br/>
	 * 1) Draws the Background colour<br/>
	 * 2) Iterates the Prescription object array list in reverse order<br/>
	 * &nbsp;&nbsp;2.1) If the Object is a Box set the line colour and fill colour<br/>
	 * &nbsp;&nbsp;2.2) If the object is a Line set all as above but without the fill colour<br/>
	 * 3) Call the method to draw the dragging of the objects<br/>
	 * 4) Call the method to draw the resizing squares for each object<br/>
	 * 5) Draw the water mark on top of the image<br/>
	 * 
	 * @param g graphics object
	 * 
	 */
	public void renderEditor(Graphics g){

		//Get instance of prescription model object
		Prescription prescription = Prescription.getInstance();

		/* Draw the background of prescription. This is a blank
		 * rectangle that has the colour of the prescription
		 * background property, if it is set, else it is white.
		 */
		if(prescription.getBGColour() != null){
			g.setColor(prescription.getBGColour());

			//The rectangle has dimensions that of prescription
			g.drawRect(0, 0, prescription.getWidth(), prescription.getHeight());
			g.fillRect(0, 0, prescription.getWidth(), prescription.getHeight());
		}
		else{
			g.clearRect(0, 0, prescription.getWidth(), prescription.getHeight());
		}

		Graphics2D g2 = (Graphics2D) g; 

		//Go through objects saved in prescription and draw each one
		//Traverse through ArrayList
		for(PrescriptionObject object : prescription.objects()){

			//Only create the fill if object is not transparent
			if(!object.isTransparent()){

				//Obtain fill colour property
				g.setColor(object.getFillColour());
				//Create a filled rectangle
				g.fillRect(object.getX(), object.getY(),
						object.getWidth(),
						object.getHeight());
			}

			//If object has a line width, create a line rectangle
			if(object.getLineWidth() > 0){
				//Obtain line width property from object
				g2.setStroke(new BasicStroke(object.getLineWidth()));

				//Set colour to object's line colour property
				g.setColor(object.getLineColour());

				//Draw the rectangle now
				g.drawRect(object.getX(), object.getY(),
						object.getWidth(),
						object.getHeight());
			}

			//If the mouse is over object and it is not already selected
			if(object.isHoverOver() && isSelected != true){
				drawSelectionRec(g, object.getX(), object.getY(),
						object.getWidth(),
						object.getHeight());
				drawResizeSquare(g, object.getResizeX(), object.getResizeY());
			}
			else if(object.isSelected()){
				drawSelectionRec(g, object.getX(), object.getY(),
						object.getWidth(),
						object.getHeight());
				drawResizeSquare(g, object.getResizeX(), object.getResizeY());
			}
						if(object instanceof PrescriptionBox){				//Text inside of object				String strName = ((PrescriptionBox) object).getName();				//Set the text colour				g.setColor(((PrescriptionBox) object).getTextColour());				//Draw the text string with right coordinates				g.drawString(strName,						object.getX() + 5,						object.getY() + 12);			}else if(object instanceof PrescriptionLabel){				//Text inside of object				String strName = ((PrescriptionLabel) object).getName();				//Set the text colour				g.setColor(((PrescriptionLabel) object).getTextColour());				//Draw the text string with right coordinates				g.drawString(strName,						object.getX() + 5,						object.getY() + 12);			}
			
		}

		//These methods call the drawing objects for the resizing and dragging modes
		drawDragging(g);
		drawResizing(g);

		//Draw the water mark after everything, e.g. on top of everything.
		g.drawImage(watermark, 0, 0, prescription.getWidth(), prescription.getHeight(),this);

	}

	/**
	 * This method draws the dragging rectangle created with a stroke of dashes.
	 * @param g the graphics object used for drawing
	 */
	public void drawDragging(Graphics g){

		//Only draw if isDrag is true, meaning if the user is dragging
		if(isDrag){
			Graphics2D gDrag = (Graphics2D) g;

			//The dragging rectangle is a dotted black coloured rectangle
			gDrag.setColor(Color.BLACK);
			gDrag.setStroke(dotted);
			g.drawRect(draggedX, draggedY, draggedWidth, draggedHeight);
		}
	}

	/**
	 * If the object is being resized create a resizing box
	 * @param g graphics object used for drawing
	 */
	private void drawResizing(Graphics g){
		if(isResize){
			Graphics2D g2 = (Graphics2D) g;

			//Set colour and stroke of drawing object
			g2.setColor(Color.BLACK);
			g2.setStroke(dotted);

			//Set default position and dimension
			int x = 0;
			int y = 0;
			int width = 0;
			int height = 0;

			if(currentX < objectX){ //Cursor has gone left to object's x
				x = currentX;
				width = objectX - currentX;
			}
			else{ //Cursor is to the right of the object's x
				x = objectX;
				width = currentX - objectX;
			}

			if(currentY < objectY){ //Cursor is above object's y
				y = currentY;
				height = objectY - currentY;
			}
			else{ //Cursor is below object's y
				y = objectY;
				height = currentY - objectY;
			}

			setResizeDimension(x, y, width, height);
			//Draw the rectangle with dashed stroke
			g2.drawRect(x, y, width, height);
		}
	}

	/**
	 * Display right click menu, when mouse is over an object
	 * @param x the x coordinate of where the menu should be displayed
	 * @param y the y coordinate of where the menu should be displayed
	 */
	public void showMenu(int x, int y){
		rightClickMenu.show(Designer.getInstance(), x, y);
	}

	/**
	 * Display menu when mouse is over background	 * @param xOnScreen the x coordinate on screen to display the menu at	 * @param yOnScreen the y coordinate on screen to display the menu at.
	 * @param x the x coordinate of where to display the menu
	 * @param y the y coordinate of where to display the menu
	 */
	public void showBGMenu(int xOnScreen, int yOnScreen, int x, int y){
		rightClickBGMenu.setCoordinates(x, y);
		rightClickBGMenu.show(Designer.getInstance(), xOnScreen, yOnScreen);
	}

	/**
	 * Sets the Text in the right click menu, either transparent
	 * or opaque
	 * @param Text
	 */
	public void setMenuText(String text){
		rightClickMenu.setTransparentText(text);
	}

	/**
	 * Prints the contents of the canvas
	 * 
	 * @param g the graphics object to print
	 * @param pf the format of the page to print
	 * @param pageIndex the page index to print
	 * 
	 */
	public int print(Graphics g, PageFormat pf, int pageIndex){
		if(pageIndex > 0){
			return NO_SUCH_PAGE;
		}
		Graphics2D g2d = (Graphics2D) g;
		g2d.translate(pf.getImageableX(), pf.getImageableY());
		this.printAll(g);
		return PAGE_EXISTS;
	}		/**	 * Checks the boundaries of the objects and checks if a box axis is close enough to be snapped to.	 */	/*public void checkBoundaries(){		Prescription pre = Prescription.getInstance();		ArrayList<PrescriptionObject> objects = pre.objects();		if(objects.size() > 1){ //useless checking boundaries on 1 or 0 objects.			PrescriptionObject selected = objects.get(pre.getSelectedIndex());			try{				if(selected instanceof PrescriptionBox){					PrescriptionBox closestBox = (PrescriptionBox)findClosestLine(selected);					if(selected.getX() > closestBox.getX() && (Math.abs(selected.getX() - (closestBox.getX() + closestBox.getWidth())) <= 20)){//we need to check the border from the right							snapToX(selected, closestBox);					}else if(selected.getX() < closestBox.getX() && (Math.abs((selected.getX() + selected.getWidth()) - closestBox.getX()) <= 20)){ 							snapToX(selected, closestBox);					}else if(selected.getY() < closestBox.getY() && (Math.abs((selected.getY() + selected.getHeight()) - closestBox.getY()) <= 20)){//we know the dragged object is higher than the snapping to object							snapToY(selected, closestBox);					}else if(selected.getY() > closestBox.getY() && (Math.abs(selected.getY() - (closestBox.getY() + closestBox.getHeight())) <= 20)){							snapToY(selected, closestBox);					}				}else{					return; //we only want to be concerned with boxes.				}							}catch(NullPointerException e){				//do nothing			}		}	}	/**	 * Finds the object with the closest line axis to a selected object	 * @param selected the object that is currently selected	 * @return the closest object to this selected object	 * @throws NullPointerException if the parameter is null, this method throws a null pointer exception	 */	/*public PrescriptionObject findClosestLine(PrescriptionObject selected)throws NullPointerException{		if(selected == null){			throw new NullPointerException("The object is null!");		}		Prescription pre = Prescription.getInstance(); 		ArrayList<PrescriptionObject> objects = pre.objects();		PrescriptionBox closestX = null; //closest box on the x axis		PrescriptionBox closestY = null; //closest box on the y axis		if(objects.size() > 0){//stops null pointers			PrescriptionBox firstBox = pre.findFirstBox();			int closestDistanceX = Math.abs((selected.getX() + selected.getWidth()) - firstBox.getX());			int closestDistanceY = Math.abs((selected.getY() + selected.getHeight()) - firstBox.getY());			closestX = firstBox;			closestY = firstBox;			for(PrescriptionObject obj: objects){ //search all the objects on the prescription				if(obj instanceof PrescriptionLabel){					continue;				}				if(Math.abs((selected.getX()+ selected.getWidth()) - (obj.getX() + obj.getWidth())) == 0){ //there is a very good chance this box is the same box as the one selected, so we skip this iteration.					continue;				}				if(Math.abs((selected.getX()+ selected.getWidth()) - obj.getX()) < closestDistanceX){					closestDistanceX = Math.abs((selected.getX() + selected.getWidth()) - obj.getX());					closestX = (PrescriptionBox)obj; //we know that this is closest box on the x axis so far				}				if(Math.abs((selected.getY() + selected.getHeight()) - obj.getY()) < closestDistanceY){					closestDistanceY = Math.abs((selected.getY() + selected.getHeight()) - obj.getY());					closestY = (PrescriptionBox)obj; //we know this is the closest box on the y axis so far				}			}			if(closestDistanceX == closestDistanceY){				return closestX; //could return either box for this.			}			if(closestDistanceX < closestDistanceY){				return closestX;			}		}		return closestY; //we know that closestDistanceX > closestDistanceY by this point so simply return it	}	/**	 * Snaps the object to the x axis of the object closest to it	 * @param dragging the object currently being dragged	 * @param snapTo the object's axis to snap to	 */	/*public void snapToX(PrescriptionObject dragging, PrescriptionObject snapTo){		Prescription pre = Prescription.getInstance();		if(Math.abs((snapTo.getX() + snapTo.getWidth()) - dragging.getX()) <= 25){ //if the objects are within a 25 pixel range of one another			pre.objects().get(pre.getSelectedIndex()).setX((snapTo.getX() + snapTo.getWidth())); //snapping to a box from the right		}else{			pre.objects().get(pre.getSelectedIndex()).setX((snapTo.getX() - dragging.getWidth())); //snapping to a box from the left		}		this.repaint();	}	/**	 * Snaps the object to the y axis of the object closest to it	 * @param dragging the object currently being dragged	 * @param snapTo the object's axis to snap to	 */	/*public void snapToY(PrescriptionObject dragging, PrescriptionObject snapTo){		Prescription pre = Prescription.getInstance();		if(Math.abs((dragging.getY() + dragging.getHeight()) - snapTo.getY()) <= 25){ //if the boxes are within 25 pixels of one another			pre.objects().get(pre.getSelectedIndex()).setY((snapTo.getY() - snapTo.getHeight())); //snapping from below		}else{			pre.objects().get(pre.getSelectedIndex()).setY((snapTo.getY() + dragging.getHeight())); //snapping from above		}		this.repaint();
	}*/}